Skip to content

feat: live score - #13

Merged
hsnice16 merged 6 commits into
mainfrom
feat/live-score
Aug 25, 2026
Merged

feat: live score#13
hsnice16 merged 6 commits into
mainfrom
feat/live-score

Conversation

@hsnice16

Copy link
Copy Markdown
Owner

Summary

  • Live Score (/score) — paste any public GitHub repo URL and get its full score on demand, for repos the leaderboard has never indexed. Same signals, same per-model breakdown, same suggestions; indexed repos redirect to their canonical /repo/:id.
  • The home page now announces each release once, on a visitor's first visit after it ships, then stays quiet until the next one.
  • Curated seed set grows by 18 repos.

Motivation

Ships tasks/0.7.0/ — cuts release 0.7.0.

▎ Goal (03-live-score-pages.md): Paste a public repo URL, get its score. Same numbers as the leaderboard, for repos the leaderboard has never seen, with nothing stored anywhere.

▎ Goal (01-tree-materializer.md): Given a public repo URL, produce a directory on disk that scoreRepo() scores identically to git clone — without a git binary, in ~1.5s for a typical repo.

The dashboard could only answer "how agent-friendly is a repo we already indexed?". That is the wrong question for anyone arriving with their own repo in mind.

Changes

Live scoring — lib/live-score/ materializes a scoreable directory from the host tree API (a Vercel function has no git binary): every path present, real bytes only for the ~14 files a signal reads. Both paths then run the untouched scoreRepo(). scripts/parity-check.ts + .github/workflows/parity.yml assert they agree, on a PR subset and nightly on the full fixture set.

Caching — reading searchParams for ?model= makes the route dynamic, so a segment-level revalidate never applies. The score is cached explicitly, keyed by repo not model. Throws are never cached, so a rate limit reaches error.tsx instead of pinning a failure on a repo for the hour.

Release notice — components/ReleaseAnnouncement.tsx, driven by CHANGELOG[0] so there is no second copy to drift. Anchored under the nav link for the page the release is about; not shown at hamburger widths, and deliberately not marked seen there either.

Fixes found along the way — bun run score now deletes each clone (36 GB had accumulated) and works outside the project, since a clone with a symlink loop breaks next build; CI now runs next build, which it never did; DB-backed API routes get cache headers.

Not shipped — GitLab and Bitbucket are implemented and score identically to a clone, but sit behind "support coming": GitLab paginates its tree at 100 entries, Bitbucket allows 60 unauthenticated requests/hour.

Testing

  • bun run test (187/187), bun x tsc --noEmit, bun x @biomejs/biome ci ., bun run build — all clean, re-run after rebasing onto main.
  • Drove the running app: scored sindresorhus/got end to end (61.9, commit e3924aa); cold 1923ms → warm 278ms, which is the evidence the caching fix works — under the original segment-level revalidate the second number would have matched the first.
  • Error paths exercised against live APIs: indexed repo redirects, nonexistent repo, unindexed GitLab repo → "support coming", console clean of errors and hydration warnings.
  • Release notice verified at 1440px, 800px, and hamburger width — including that a hamburger-width visit does not consume the announcement (widen, reload, it still shows).
  • All 370 seeds round-trip through parseRepoUrl with 0 mismatches; every new seed checked against its host API as public, non-fork, non-archived, not renamed.

Docs + roadmap sync

Ships a roadmap item — removed from lib/roadmap.ts (0.7.0 → 0.8.0), added to lib/changelog.ts under the 0.7.0 bucket. AGENTS.md and README.md updated for the new directories, the parity-check command, the live-score threat surface, and a new client-side-persistence convention; CONTRIBUTING.md documents the parity workflow. The code-review and quality-check skills' JSON-LD allowlists were stale and are now in step with AGENTS.md.

Risks / rollback

  • GITHUB_TOKEN must be set in the Vercel environment before merge. Unauthenticated, api.github.com allows 60 requests/hour per IP and serverless egress IPs are shared, so /score would
    fail for everyone at once. Tokenized it is 5,000/hour.
  • Cost is bounded by distinct uncached repos, not traffic. Cache hits never invoke a function. A crawl of distinct slugs is the exposure; robots.ts disallows /score/, the result page is
    noindex, and a slug-shape guard rejects probes before any fetch. Per-IP rate limiting needs shared state and is deferred until abuse actually appears.
  • The 18 new seeds add clone time to the 6-hourly rescore, which has a 60-minute cap. I could not measure real run durations from here, so this is worth a glance at the next few runs.
  • Rollback: revert the merge. Nothing here writes to data/rank.db or changes the schema, and lib/scoring/ is untouched, so the leaderboard and both sibling repos are unaffected.

hsnice16 and others added 6 commits August 25, 2026 16:37
/score takes a repository URL and returns the full score — signals,
per-model breakdown, suggestions — for repos the leaderboard has never
indexed. Indexed repos redirect to their canonical /repo/:id page.

A Vercel function has no git binary, so the live path materializes the
tree from the host API instead of cloning: every path present, real bytes
only for the ~14 files a signal reads. Both paths then run the same
scoreRepo(), and scripts/parity-check.ts asserts they agree — a signal
that starts reading a new file would otherwise score as empty on the live
path only, with no error and a wrong number on a public page.

Reading searchParams for ?model= makes the route dynamic, so a
segment-level revalidate would never cache the render. The score is
cached explicitly instead, keyed by repo rather than model. Throws are
not cached, so a rate limit or host blip reaches error.tsx rather than
being pinned on a repo for the hour.

GitLab and Bitbucket are implemented and score identically to a clone,
but ship behind "support coming": GitLab paginates its tree at 100
entries and Bitbucket allows 60 unauthenticated requests an hour.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
/changelog has always carried this and nobody goes looking for it; the
version pill is a fact with no story attached. The notice renders
CHANGELOG[0].title and nothing else, so there is no second copy of the
release description to drift.

The seen marker stores the version rather than a boolean, which is what
makes the next release show again with no reset step or expiry logic. It
is written at the moment of display, not on dismiss, so "exactly once per
release" holds for a visitor who leaves after two seconds.

Anchored under the nav link for the page the release is about, measured
from that link's own rect — the nav's contents decide where it lands and
they change. Below md the header collapses to a hamburger, so there is
nothing to point at and nothing renders; the marker is deliberately not
written in that case, or the one announcement would be spent on a screen
that never showed it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ject

`bun run seed` scores 350+ repos and kept every tree, which had grown to
36 GB locally. Nothing downstream reads the tree again — the score is
already in memory by then.

The workspace also moves from ./tmp-clones to the OS temp dir, because a
clone inside the project is part of the Next.js module graph: one repo
with a symlink loop (stripe/ai has one) fails `next build` with a
Turbopack panic that names none of this.

The dynamic join/readdirSync calls in the scorer make the tracer give up
and pull the whole project into every function bundle. The scorer can't
be annotated away — it is vendored verbatim into both sibling repos — so
the weight is trimmed with outputFileTracingExcludes instead.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
tsc does not see what only the bundler sees — a server-only import pulled
into a client component, an RSC boundary violation. Without this the first
place a broken build shows up is the Vercel deploy.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
data/rank.db ships inside the deployment, so these responses cannot change
until the next deploy. Uncached, every caller re-serialised the table —
/api/repos returns the whole leaderboard. Matches the headers the badge
and package routes already set.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Weighted toward the thin sections rather than JS/TS: Swift and Dart gain
two each, PHP two, GitLab two. gitlab.com/kicad/code/kicad is a subgroup
path, which exercises the nested-namespace branch of parseRepoUrl that
only gitlab-org/* covered.

Each verified against its host API as public, non-fork, non-archived and
not renamed — the criteria audit-seeds.ts enforces.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@vercel

vercel Bot commented Aug 25, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
agent-friendly-code Ready Ready Preview Aug 25, 2026 11:21am

@github-actions

Copy link
Copy Markdown
Contributor

Agent Friendly Code — score diff

Overall: 91.0 → 91.0 (±0.0)

Per model

Model Base Head Δ
Claude Code 95.3 95.3 ±0.0
Cursor 85.5 85.5 ±0.0
Devin 94.8 94.8 ±0.0
GPT-5 Codex 96.3 96.3 ±0.0
Kimi CLI 96.1 96.1 ±0.0
Gemini CLI 85.5 85.5 ±0.0
Aider 83.1 83.1 ±0.0
OpenHands 85.4 85.4 ±0.0
Pi 96.6 96.6 ±0.0

No signals changed between base and head.


Posted by agent-friendly-action.

@hsnice16
hsnice16 merged commit 0758fc3 into main Aug 25, 2026
5 checks passed
@hsnice16
hsnice16 deleted the feat/live-score branch August 25, 2026 11:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant